using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Input;
using Caliburn.Micro;
using Paladyne.FileSearch.Core;
using Paladyne.FileSearch.UI.Properties;
using KeyEventArgs = System.Windows.Input.KeyEventArgs;

namespace Paladyne.FileSearch.UI
{
    /// <summary>
    /// Main ViewModel for the application UI
    /// </summary>
    [Export(typeof (IShell))]
    public sealed class ShellViewModel : Conductor<object>, IShell
    {
        private const int KilobytesMultiplier = 1024;
        private const String LessThan = "<";
        private const String GreaterThan = ">";

        private readonly ITextCompareStrategy _compareStrategy;
        private readonly string[] _fileSizeOperators = new[] {LessThan, GreaterThan};
        private readonly IDisplayMessage _messageDisplayer;
        private readonly IDictionary<string, object> _moduleViewModels;
        private readonly ISearcher _searcher;

        private readonly BackgroundWorker _worker = new BackgroundWorker
                                                        {
                                                            WorkerReportsProgress = true,
                                                            WorkerSupportsCancellation = true
                                                        };

        private bool _canCancelSearch;
        private bool _canStartSearch = true;
        private string _directoryName;
        private string _searchResultText;
        private string _selectedFileSizeOperator;

        #region Data binding properties

        public string DirectoryName
        {
            get { return _directoryName; }

            set
            {
                _directoryName = value;
                NotifyOfPropertyChange(() => DirectoryName);
                NotifyOfPropertyChange(() => CanStartSearch);
            }
        }

        public string SearchString { get; set; }
        public bool SearchInSubdirectories { get; set; }

        public BindableCollection<string> SearchModuleNames { get; set; }

        public string[] FileSizeOperators
        {
            get { return _fileSizeOperators; }
        }

        public long FileSize { get; set; }
        public DateTime DateFrom { get; set; }
        public DateTime DateTo { get; set; }
        public bool? AttributeArchived { get; set; }
        public bool? AttributeHidden { get; set; }
        public bool? AttributeEncrypted { get; set; }
        public bool? AttributeReadonly { get; set; }
        public bool? AttributeSystem { get; set; }
        public bool? AttributeCompressed { get; set; }
        public bool FileSizeEnabled { get; set; }
        public bool DateEnabled { get; set; }
        public bool AttributesEnabled { get; set; }

        // This is actually a Model
        public BindableCollection<string> SearchResults { get; private set; }

        public bool CanStartSearch
        {
            get { return _canStartSearch && !String.IsNullOrWhiteSpace(DirectoryName); }
            set
            {
                _canStartSearch = value;
                NotifyOfPropertyChange(() => CanStartSearch);
            }
        }

        public bool CanCancelSearch
        {
            get { return _canCancelSearch; }
            set
            {
                _canCancelSearch = value;
                NotifyOfPropertyChange(() => CanCancelSearch);
            }
        }

        public string SearchResultText
        {
            get { return _searchResultText; }
            set
            {
                _searchResultText = value;
                NotifyOfPropertyChange(() => SearchResultText);
            }
        }

        #endregion

        #region Constructor

        public ShellViewModel()
        {
            DisplayName = Resources.ShellViewModel_ShellViewModel_File_search_tool;
            DirectoryName = DriveInfo.GetDrives()[0].RootDirectory.FullName;
            DateTo = DateTime.Now;
            _searcher = IoC.Get<ISearcher>();
            _compareStrategy = IoC.Get<ITextCompareStrategy>();
            _messageDisplayer = IoC.Get<IDisplayMessage>();
            _moduleViewModels = new Dictionary<string, object>();
            SearchModuleNames = new BindableCollection<string>();
            SearchResults = new BindableCollection<string>();
            SetupSearchModules();
            _selectedFileSizeOperator = _fileSizeOperators[0];

            _worker.DoWork += DoBackgroundSearch;
            _worker.ProgressChanged += BackgroundSearchProgressChanged;
            _worker.RunWorkerCompleted += BackgroundSearchCompleted;
        }

        #endregion

        #region Background search methods

        /// <summary>
        /// Starts a search when Enter key is pressed
        /// </summary>
        /// <param name="e"></param>
        public void KeyPressedOnView(KeyEventArgs e)
        {
            if (e.Key == Key.Enter && CanStartSearch)
                StartSearch();
        }

        /// <summary>
        /// Start file search asynchronously
        /// </summary>
        public void StartSearch()
        {
            _worker.RunWorkerAsync(new SearchParameters(DirectoryName, SearchString, SearchInSubdirectories));
        }

        /// <summary>
        /// Cancel file search asynchronously
        /// </summary>
        public void CancelSearch()
        {
            _worker.CancelAsync();
        }

        /// <summary>
        /// Perform the search and update the search results list
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DoBackgroundSearch(object sender, DoWorkEventArgs e)
        {
            CanStartSearch = false;
            CanCancelSearch = true;
            SearchResultText = String.Empty;

            var parameters = e.Argument as SearchParameters;

            if (parameters == null)
                throw new ArgumentNullException("e.Argument",
                                                Resources.
                                                    ShellViewModel_DoBackgroundSearch_Search_parameters_cannot_be_null);

            lock (SearchResults)
            {
                SearchResults.Clear();
                int fileCount = 0;
                foreach (FileInfo fileInfo in _searcher.Search(parameters, GetConditions()))
                {
                    if (_worker.CancellationPending)
                    {
                        e.Cancel = true;
                        break;
                    }
                    SearchResults.Add(fileInfo.FullName);
                    fileCount++;
                    _worker.ReportProgress(0,
                                           String.Format(
                                               Resources.ShellViewModel_DoBackgroundSearch__0__files_found_so_far___,
                                               fileCount));
                }

                // If nothing has been found
                if (!SearchResults.Any())
                {
                    e.Result = Resources.ShellViewModel_DoBackgroundSearch_No_files_found;
                }
                else
                {
                    e.Result = String.Format(Resources.ShellViewModel_DoBackgroundSearch_Complete___0__files_found,
                                             fileCount);
                }
            }
        }

        /// <summary>
        /// Handles search progress change
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BackgroundSearchProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            SearchResultText = e.UserState.ToString();
        }

        /// <summary>
        /// Handles search completion
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BackgroundSearchCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            CanCancelSearch = false;
            CanStartSearch = true;
            SearchResultText = !e.Cancelled
                                   ? e.Result.ToString()
                                   : Resources.ShellViewModel_BackgroundSearchCompleted_Cancelled_by_user;
            if (e.Error != null)
                _messageDisplayer.ShowError(
                    Resources.ShellViewModel_BackgroundSearchCompleted_An_error_occured_while_searching_in_background__ +
                    e.Error.Message);
        }

        #endregion

        #region Search module related methods

        /// <summary>
        /// Loads all found search modules and adds them to datasource of a combobox
        /// </summary>
        private void SetupSearchModules()
        {
            IEnumerable<object> moduleViewModels = IoC.GetAllInstances(typeof (ISearchModule));

            lock (_moduleViewModels)
            {
                _moduleViewModels.Clear();
                foreach (object moduleViewModel in moduleViewModels)
                {
                    var viewModel = (ISearchModule) moduleViewModel;
                    _moduleViewModels.Add(viewModel.Extension, moduleViewModel);

                    if (!SearchModuleNames.Contains(viewModel.Extension))
                        SearchModuleNames.Add(viewModel.Extension);
                }
            }

            if (_moduleViewModels.Any())
                SetCurrentSearchModule(_moduleViewModels.First().Key);
        }

        /// <summary>
        /// Activates the selected search module and displays the corresponding control
        /// </summary>
        /// <param name="s"></param>
        public void SetCurrentSearchModule(string s)
        {
            if (_moduleViewModels.ContainsKey(s))
            {
                _searcher.SearchModule = ((ISearchModule) _moduleViewModels[s]);
                ActivateItem(_moduleViewModels[s]);
                if (SearchResults.Any())
                    SearchResults.Clear();
                SearchResultText = String.Empty;
            }
        }

        /// <summary>
        /// Checks for new modules in extensions folder and loads new search modules (if any)
        /// </summary>
        public void RefreshModules()
        {
            App.RefreshExtensions();
            SetupSearchModules();
        }

        #endregion

        #region Filtering conditions preparation

        /// <summary>
        /// Collects the file search conditions
        /// </summary>
        /// <returns></returns>
        private IEnumerable<Predicate<FileInfo>> GetConditions()
        {
            var conditions = new List<Predicate<FileInfo>>();

            // Add extension filter
            if (_searcher != null && _searcher.SearchModule != null)
            {
                conditions.Add(f => _compareStrategy.AreEqual(f.Extension, _searcher.SearchModule.Extension, true));
            }

            // Add file name fragment to search
            if (!String.IsNullOrWhiteSpace(SearchString))
                conditions.Add(
                    f => _compareStrategy.Contains(Path.GetFileNameWithoutExtension(f.Name), SearchString.Trim(), true));

            // Add file size filter
            if (FileSizeEnabled && FileSize > 0)
            {
                if (_selectedFileSizeOperator == LessThan)
                    conditions.Add(f => f.Length <= FileSize*KilobytesMultiplier);
                if (_selectedFileSizeOperator == GreaterThan)
                    conditions.Add(f => f.Length >= FileSize*KilobytesMultiplier);
            }

            // Add last modification date filter
            if (DateEnabled)
            {
                if (DateFrom > DateTime.MinValue && DateFrom < DateTime.MaxValue)
                {
                    conditions.Add(f => f.LastWriteTime >= DateFrom);
                }

                if (DateTo > DateTime.MinValue && DateTo < DateTime.MaxValue)
                {
                    conditions.Add(f => f.LastWriteTime <= DateTo);
                }
            }

            // Add file attribute filters
            if (AttributesEnabled)
            {
                var attributes = new[]
                                     {
                                         new
                                             {
                                                 AttributeProperty = AttributeArchived,
                                                 AttributeName = FileAttributes.Archive
                                             },
                                         new
                                             {
                                                 AttributeProperty = AttributeHidden,
                                                 AttributeName = FileAttributes.Hidden
                                             },
                                         new
                                             {
                                                 AttributeProperty = AttributeEncrypted,
                                                 AttributeName = FileAttributes.Encrypted
                                             },
                                         new
                                             {
                                                 AttributeProperty = AttributeReadonly,
                                                 AttributeName = FileAttributes.ReadOnly
                                             },
                                         new
                                             {
                                                 AttributeProperty = AttributeSystem,
                                                 AttributeName = FileAttributes.System
                                             },
                                         new
                                             {
                                                 AttributeProperty = AttributeCompressed,
                                                 AttributeName = FileAttributes.Compressed
                                             },
                                     };

                foreach (var attribute in attributes)
                {
                    if (attribute.AttributeProperty.HasValue)
                    {
                        var attr = attribute;
                        if (attribute.AttributeProperty.Value)
                            conditions.Add(f => f.Attributes.HasFlag(attr.AttributeName));
                        else
                            conditions.Add(f => !f.Attributes.HasFlag(attr.AttributeName));
                    }
                }
            }

            return conditions;
        }

        /// <summary>
        /// Remembers the selected file size comparison operator
        /// </summary>
        /// <param name="fileSizeOperators"></param>
        public void ChangeFileSizeOperator(string fileSizeOperators)
        {
            _selectedFileSizeOperator = fileSizeOperators;
        }

        #endregion

        /// <summary>
        /// Allows user to select a directory for searching
        /// </summary>
        public void SelectFolder()
        {
            var dialog = new FolderBrowserDialog
                             {
                                 Description =
                                     Resources.ShellView_btnSelectFolder_Click_Please_select_a_folder_to_search_in,
                                 ShowNewFolderButton = false
                             };

            if (dialog.ShowDialog() == DialogResult.OK)
                DirectoryName = dialog.SelectedPath;
        }
    }
}